<?xml version="1.0" encoding="utf-8"?>

<overlay xmlns="http://hoa-project.net/xyl/xylophone">
<yield id="chapter">

  <p>Le terminal est une <strong>interface</strong> très
  <strong>puissante</strong> qui repose sur de multiples concepts.
  <code>Hoa\Console</code> permet d'écrire des <strong>outils</strong> adaptés à
  ce type d'environnement.</p>

  <h2 id="Table_of_contents">Table des matières</h2>

  <tableofcontents id="main-toc" />

  <h2 id="Introduction" for="main-toc">Introduction</h2>

  <p>De nos jours, nous comptons deux types d'interfaces :
  <strong>textuelle</strong> et <strong>graphique</strong>. L'interface
  textuelle existe depuis l'origine des ordinateurs, alors appelés
  <strong>terminaux</strong>. Cette interface, malgré son aspect « brut », est
  fonctionnellement très <strong>puissante</strong> grâce à plusieurs concepts
  comme par exemple la ligne de commande ou les <em lang="en">pipes</em>.
  Aujourd'hui, elle est encore très utilisée car elle est souvent plus rapide
  pour exécuter des tâches <strong>complexes</strong> qu'une interface
  graphique. Elle peut être aussi très facilement utilisée à travers des réseaux
  ou sur des machines à faibles ressources. Bref, cette interface est toujours
  <strong>incontournable</strong>.</p>
  <p>Du point de vue de l'utilisateur, il y a trois niveaux à considérer :</p>
  <ul>
    <li>l'<strong>interface</strong> : afficher et éditer du texte, manipuler la
    fenêtre, le curseur etc. ;</li>
    <li>le <strong>programme</strong> : interagir avec l'utilisateur avec un
    maximum de confort, utiliser la ligne de commande à son plein potentiel,
    construire des programmes adaptés à ce type d'interface ;</li>
    <li>l'<strong>interaction</strong> avec d'autres programmes : interagir
    automatiquement et communiquer avec d'autres programmes.</li>
  </ul>
  <p>La bibliothèque <code>Hoa\Console</code> propose des outils pour répondre à
  ces trois niveaux de problématique. Pour cela, elle se base sur des
  <strong>standards</strong>, comme
  l'<a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf">ECMA-48</a>
  qui spécifie la communication avec le système à travers des suites de
  caractères ASCII et des codes de contrôle (aussi appelés séquences
  d'échappement), ce afin de manipuler la fenêtre, le curseur ou des
  périphériques de la machine. D'autres fonctionnalités sont aussi standards
  comme la manière de lire des options depuis un programme, très
  <strong>inspirée</strong> de systèmes comme
  <a href="http://linux.org/">Linux</a>, <a href="http://freebsd.org/">FreeBSD</a> ou
  encore <a href="https://en.wikipedia.org/wiki/UNIX_System_V">System V</a>.
  D'ailleurs, si vous êtes familier avec plusieurs bibliothèques C, vous ne
  serez pas déroutés. Et <em>a contrario</em>, si vous apprenez à utiliser
  <code>Hoa\Console</code>, vous ne serez pas perdus en retournant sur des
  langages de plus bas niveaux comme le C.</p>
  <p>Avant de commmencer, nous aimerions ajouter une petite note
  <strong>uniquement</strong> à propos de la gestion de la fenêtre et du
  curseur. Aujourd'hui, nous avons le choix entre <strong>plusieurs</strong>
  terminaux par système et certains sont plus complets que d'autres. Par
  exemple, <a href="https://windows.microsoft.com/">Windows</a> et son terminal
  par défaut, le <a href="http://en.wikipedia.org/wiki/MS-DOS">MS-DOS</a>, ne
  respecte aucun standard. Dans ce cas, oubliez le standard ECMA-48 et
  tournez-vous vers
  <a href="http://msdn.microsoft.com/library/ms682087.aspx"
     title="Console Reference">la bibliothèque <code>Wincon</code></a>. Il est
  souvent recommandé d'utiliser une machine Unix <strong>virtuelle</strong> ou
  un <strong>émulateur</strong> de terminal, comme
  <a href="http://ttssh2.sourceforge.jp/">TeraTerm</a>, très complet. Même sur
  des systèmes proches de la famille BSD, les terminaux distribués par défaut ne
  supportent pas tous les standards. C'est le cas de Mac OS X, où nous vous
  conseillons d'utiliser <a href="http://iterm2.com">iTerm2</a> au lieu de
  Terminal. Enfin, sur d'autres systèmes de la famille Linux ou BSD, nous
  conseillons
  <a href="http://software.schmorp.de/pkg/rxvt-unicode.html">urxvt</a>. Pour
  les autres fonctionnalités, comme la lecture en ligne, la lecture d'options,
  les processus etc., <code>Hoa\Console</code> est parfaitement
  <strong>compatible</strong> et fonctionnel.</p>

  <h2 id="Window" for="main-toc">Fenêtre</h2>

  <p>La fenêtre d'un terminal doit être vue comme un <strong>canevas</strong> de
  <strong>colonnes</strong> et de <strong>lignes</strong>. La classe
  <code>Hoa\Console\Window</code> permet de manipuler la
  <strong>fenêtre</strong> du terminal et son <strong>contenu</strong> à travers
  des méthodes statiques.</p>

  <h3 id="Size_and_position" for="main-toc">Taille et position</h3>

  <p>Les premières opérations élémentaires concernent la <strong>taille</strong>
  et la <strong>position</strong> de la fenêtre, grâce aux méthode
  <code>setSize</code>, <code>getSize</code>, <code>moveTo</code> et
  <code>getPosition</code>. La taille se définie avec les unités
  <em>colonne</em> × <em>ligne</em> et la position se définie en pixels.
  Ainsi :</p>
  <pre><code class="language-php">Hoa\Console\Window::setSize(80, 50);
print_r(Hoa\Console\Window::getSize());
print_r(Hoa\Console\Window::getPosition());

/**
 * Will output:
 *     Array
 *     (
 *         [x] => 80
 *         [y] => 50
 *     )
 *     Array
 *     (
 *         [x] => 104
 *         [y] => 175
 *     )
 */</code></pre>
  <p>Nous remarquerons que la fenêtre se redimensionne <strong>toute
  seule</strong>. Ni la taille ni la position de la fenêtre ne sont stockées en
  mémoire, elles sont calculées à chaque appel de la méthode
  <code>getSize</code> et <code>getPosition</code>. Attention, l'axe <em>y</em>
  de la position de la fenêtre se calcule depuis <strong>le bas</strong> de
  l'écran et non pas depuis le haut de l'écran comme nous pourrions nous y
  attendre !</p>
  <p>Il est aussi possible d'écouter l'<strong>événement</strong>
  <code>hoa://Event/Console/Window:resize</code> qui est lancé à chaque fois que
  la fenêtre est redimensionnée : soit manuellement, soit avec la méthode
  <code>setSize</code>. Nous avons besoin de deux choses pour que cet événement
  fonctionne :</p>
  <ol>
    <li><a href="http://php.net/pcntl">l'extension <code>pcntl</code></a> doit
    être activée ;</li>
    <li>nous devons utiliser
    <a href="http://php.net/declare">la structure <code>declare</code></a> pour
    que <a href="http://php.net/pcntl_signal">la fonction
    <code>pcntl_signal</code></a> fonctionne correctement.</li>
  </ol>
  <p>Pour mettre le programme en attente passive, nous allons utiliser
  <a href="http://php.net/stream_select">la fonction
  <code>stream_select</code></a>, c'est un <strong>détail</strong> présent
  uniquement pour tester notre code, sinon le programme se terminerait tout de
  suite. Ainsi :</p>
  <pre><code class="language-php">Consistency\Autoloader::load('Hoa\Console\Window'); // make sure it is loaded.

declare(ticks = 1);

Hoa\Event\Event::getEvent('hoa://Event/Console/Window:resize')
    ->attach(function (Hoa\Event\Bucket $bucket) {
        $data = $bucket->getData();
        $size = $data['size'];

        echo 'New size (', $size['x'], ', ', $size['y'], ')', "\n";
    });

// Passive loop.
while (true) {
    $r = [STDIN];
    @stream_select($r, $w, $e, 3600);
}</code></pre>
  <p>Lorsque nous modifions la taille de la fenêtre, nous verrons s'afficher par
  exemple : <samp>New size (45, 67)</samp>, et ce pour chaque redimensionnement.
  Cet événement est intéressant si nous voulons <strong>ré-adapter</strong>
  notre présentation.</p>
  <p>Enfin, nous pouvons minimiser ou restaurer la fenêtre grâce aux méthodes
  statiques <code>Hoa\Console\Window::minimize</code> et
  <code>Hoa\Console\Window::restore</code>. Par ailleurs, nous pouvons placer la
  fenêtre en arrière-plan (derrière toutes les autres fenêtres) grâce à la
  méthode statique <code>Hoa\Console\Window::lower</code>, tout comme nous
  pouvons la placer en avant-plan avec <code>Hoa\Console\Window::raise</code>.
  Par exemple :</p>
  <pre><code class="language-php">Hoa\Console\Window::minimize();
sleep(2);
Hoa\Console\Window::restore();
sleep(2);
Hoa\Console\Window::lower();
sleep(2);
Hoa\Console\Window::raise();

echo 'Back!', "\n";</code></pre>

  <h3 id="Title_and_label" for="main-toc">Titre et label</h3>

  <p>Le <strong>titre</strong> d'une fenêtre correspond au texte affiché dans sa
  <strong>barre</strong> supérieure, dans laquelle sont souvent placés les
  contrôles de la fenêtre comme la maximisation, la minimisation etc. Le
  <strong>label</strong> correspond au nom associé au <strong>processus</strong>
  actuel. Nous trouvons les méthodes <code>setTitle</code>,
  <code>getTitle</code> et <code>getLabel</code>, il n'est pas prévu de modifier
  le label. Pour définir le titre du processus (ce que nous voyons avec la
  commande <code>top</code> ou <code>ps</code> par exemple), il faudra se
  référer à <code>Hoa\Console\Processus::setTitle</code> et à
  <code>Hoa\Console\Processus::getTitle</code> pour l'obtenir. Ainsi :</p>
  <pre><code class="language-php">Hoa\Console\Window::setTitle('Foobar');
var_dump(Hoa\Console\Window::getTitle());
var_dump(Hoa\Console\Window::getLabel());

/**
 * Will output:
 *     string(6) "Foobar"
 *     string(3) "php"
 */</code></pre>
  <p>Encore une fois, le titre et le label ne sont pas stockés en mémoire, ils
  sont calculés à chaque appel de méthode.</p>

  <h3 id="Interact_with_the_content" for="main-toc">Interagir avec le
  contenu</h3>

  <p><code>Hoa\Console\Window</code> permet aussi de contrôler le
  <strong>contenu</strong> de la fenêtre, ou du moins le
  <em lang="en">viewport</em>, c'est à dire le contenu <strong>visible</strong>
  de la fenêtre. Une seule méthode est actuellement disponible :
  <code>scroll</code>, qui permet de <strong>déplacer</strong> le contenu vers
  le haut ou vers le bas. Les arguments de cette méthode sont très simples :
  <code>up</code> ou <code>↑</code> pour monter d'une ligne, et
  <code>down</code> ou <code>↓</code> pour descendre d'une ligne. Nous pouvons
  concaténer ces directions par un espace ou alors préciser le nombre de fois où
  une direction sera répétée :</p>
  <pre><code class="language-php">Hoa\Console\Window::scroll('↑', 10);</code></pre>
  <p>En réalité, cette méthode va déplacer le contenu pour qu'il y ait
  <em>x</em> lignes respectivement en-dessous ou au-dessus du curseur.
  Attention, le curseur <strong>ne change pas</strong> de position !</p>
  <p>Même si c'est très souvent inutile, il est possible de
  <strong>rafraîchir</strong> la fenêtre, c'est à dire de refaire un rendu
  complet. Nous pouvons nous aider de la méthode <code>refresh</code> toujour
  sur <code>Hoa\Console\Window</code>.</p>
  <p>Enfin, il est possible de placer un texte dans le
  <strong>presse-papier</strong> de l'utilisateur à l'aide de la méthode
  <code>copy</code> :</p>
  <pre><code class="language-php">Hoa\Console\Window::copy('Foobar');</code></pre>
  <p>Puis si l'utilisateur colle ce qui est dans son presse-papier, il verra
  <samp>Foobar</samp> s'afficher.</p>

  <h2 id="Cursor" for="main-toc">Curseur</h2>

  <p>À l'intérieur d'une fenêtre, nous avons un curseur qui peut être vu comme
  la <strong>pointe</strong> d'un stylo. La classe
  <code>Hoa\Console\Cursor</code> permet de manipuler le
  <strong>curseur</strong> du terminal à travers des méthodes statiques.</p>

  <h3 id="Moving" for="main-toc">Déplacement</h3>

  <p>Nous allons commencer par <strong>déplacer</strong> le curseur. Il se
  déplace partout dans le <em lang="en">viewport</em>, c'est à dire le contenu
  <strong>visible</strong> de la fenêtre du terminal, mais nous allons écrire un
  peu de texte et nous déplacer dedans dans un premier temps. La méthode
  <code>move</code> sur <code>Hoa\Console\Cursor</code> permet de déplacer le
  curseur dans plusieurs <strong>directions</strong>. Tout d'abord de manière
  <strong>relative</strong> :</p>
  <ul>
    <li><code>u[p]</code> ou <code>↑</code>, pour le déplacer à la ligne
    supérieure ;</li>
    <li><code>r[ight]</code> ou <code>→</code>, pour le déplacer à la colonne
    suivante ;</li>
    <li><code>d[own]</code> ou <code>↓</code>, pour le déplacer à la ligne
    inférieure ;</li>
    <li><code>l[eft]</code> ou <code>←</code>, pour le déplacer à la colonne
    précédente.</li>
  </ul>
  <p>Nous trouvons aussi des déplacements <strong>semi-absolus</strong> :</p>
  <ul>
    <li><code>U[P]</code>, pour le déplacer à la première ligne du
    <em lang="en">viewport</em> ;</li>
    <li><code>R[IGHT]</code>, pour le déplacer à la dernière colonne du
    <em lang="en">viewport</em> ;</li>
    <li><code>D[OWN]</code>, pour le déplacer à la dernière ligne du
    <em lang="en">viewport</em> ;</li>
    <li><code>L[EFT]</code>, pour le déplacer à la première colonne du
    <em lang="en">viewport</em>.</li>
  </ul>
  <p>Ces directions peuvent être concaténées par des espaces, ou alors nous
  pouvons préciser le nombre de fois où une direction sera répétée.</p>
  <pre><code class="language-php">echo
    'abcdef', "\n",
    'ghijkl', "\n",
    'mnopqr', "\n",
    'stuvwx';

sleep(1);
Hoa\Console\Cursor::move('↑');
sleep(1);
Hoa\Console\Cursor::move('↑ ←');
sleep(1);
Hoa\Console\Cursor::move('←', 3);
sleep(1);
Hoa\Console\Cursor::move('DOWN');
sleep(1);
Hoa\Console\Cursor::move('→', 4);</code></pre>
  <p>Lors de l'exécution, nous verrons le curseur se déplacer <strong>tout
  seul</strong> de « lettre en lettre » toutes les secondes.</p>
  <p>Pour réellement déplacer le curseur de manière <strong>absolue</strong>,
  nous utiliserons la méthode <code>moveTo</code> qui prend en argument des
  coordonnées en <em>colonne</em> × <em>ligne</em> (la numérotation commence à 1
  et non pas à 0). Nous en profitons pour parler de la méthode
  <code>getPosition</code> qui permet de connaître la <strong>position</strong>
  du curseur. Ainsi, si nous voulons déplacer le curseur à la colonne 12 et à la
  ligne 7, puis afficher ces coordonnées, nous écrirons :</p>
  <pre><code class="language-php">Hoa\Console\Cursor::moveTo(12, 7);
print_r(Hoa\Console\Cursor::getPosition());

/**
 * Will output:
 *     Array(
 *         [x] => 12
 *         [y] => 7
 *     )
 */</code></pre>
  <p>Enfin, il arrive très régulièrement que nous voulions déplacer le curseur
  <strong>temporairement</strong> pour quelques opérations. Dans ce cas, il est
  inutile de récupérer la position actuelle, le déplacer, puis le
  repositionner ; nous pouvons profiter des méthodes <code>save</code> et
  <code>restore</code>. Comme leur nom l'indique, ces méthodes respectivement
  <strong>enregistre</strong> la position du curseur puis
  <strong>restaure</strong> le curseur à la position précédemment enregistrée.
  Ces fonctions ne manipulent pas de <strong>pile</strong>, il est impossible
  d'enregistrer plus d'une seule position à la fois (le nouvel enregistrement
  <strong>écrasera</strong> l'ancien). Ainsi, nous allons écrire un texte,
  enregistrer la position du curseur, revenir en arrière et réécrire par dessus,
  pour enfin revenir à notre position précédente :</p>
  <pre><code class="language-php">echo 'hello world';

// Save cursor position.
Hoa\Console\Cursor::save();
sleep(1);

// Go to the begining of the line.
Hoa\Console\Cursor::move('LEFT');
sleep(1);

// Replace “h” by “H”.
echo 'H';
sleep(1);

// Go to “w”.
Hoa\Console\Cursor::move('→', 5);
sleep(1);

// Replace “w” by “W”.
echo 'W';
sleep(1);

// Back to the saved position.
Hoa\Console\Cursor::restore();
sleep(1);

echo '!';</code></pre>
  <p>Le résultat final sera <samp>Hello World!</samp>. Nous remarquons qu'à
  chaque fois qu'un caractère est écrit, le curseur se
  <strong>déplace</strong>.</p>

  <h3 id="Content" for="main-toc">Affichage</h3>

  <p>Maintenant que le déplacement est acquis, nous allons voir comment
  <strong>nettoyer</strong> des lignes et/ou des colonnes. Pour cela, nous nous
  appuyons sur la méthode <code>clear</code> qui prend en argument les symboles
  suivants (concaténés par un espace) :</p>
  <ul>
    <li><code>a[ll]</code> ou <code>↕</code>, pour nettoyer tout l'écran et
    déplacer le curseur en haut à gauche du <em lang="en">viewport</em> ;</li>
    <li><code>u[p]</code> ou <code>↑</code>, pour nettoyer toutes les lignes
    au-dessus du curseur ;</li>
    <li><code>r[ight]</code> ou <code>→</code>, pour nettoyer le reste de la
    ligne à partir du curseur ;</li>
    <li><code>d[own]</code> ou <code>↓</code>, pour nettoyer toutes les lignes
    en-dessous du curseur ;</li>
    <li><code>l[eft]</code> ou <code>←</code>, pour nettoyer du début de la
    ligne jusqu'au curseur ;</li>
    <li><code>line</code> ou <code>↔</code>, pour nettoyer toute la ligne et
    déplacer le curseur en début de ligne.</li>
  </ul>
  <p>Ainsi, pour nettoyer <strong>toute une ligne</strong> :</p>
  <pre><code class="language-php">Hoa\Console\Cursor::clear('↔');</code></pre>
  <p>Le curseur peut aussi agir comme un <strong>pinceau</strong> et ainsi
  écrire avec différentes <strong>couleurs</strong> ou différents
  <strong>styles</strong> grâce à la méthode <code>colorize</code> (nous pouvons
  tout mélanger en séparant chaque « commande » par des espaces). Commençons
  par énumérer les styles :</p>
  <ul>
    <li><code>n[ormal]</code>, pour annuler tous les styles appliqués ;</li>
    <li><code>b[old]</code>, pour écrire en gras ;</li>
    <li><code>u[nderlined]</code>, pour avoir un texte souligné ;</li>
    <li><code>bl[ink]</code>, pour avoir un texte qui clignote ;</li>
    <li><code>i[nverse]</code>, pour inverser les couleurs d'avant et
    d'arrière-plan ;</li>
    <li><code>!b[old]</code>, pour annuler le gras ;</li>
    <li><code>!u[nderlined]</code>, pour annuler le soulignement ;</li>
    <li><code>!bl[ink]</code>, pour annuler le clignotement ;</li>
    <li><code>!i[nverse]</code>, pour ne plus inverser les couleurs d'avant et
    d'arrière-plan.</li>
  </ul>
  <p>Ces styles sont très classiques. Passons maintenant aux couleurs. Tout
  d'abord, nous devons préciser si nous appliquons une couleur sur
  l'<strong>avant-plan</strong> du texte, soit le texte lui-même, ou alors sur
  son <strong>arrière-plan</strong>. Pour cela, nous allons nous aider
  respectivement de la syntaxe <code>f[ore]g[round](<em>color</em>)</code> et
  <code>b[ack]g[round](<em>color</em>)</code>. La valeur de
  <code><em>color</em></code> peut être :</p>
  <ul>
    <li><code>default</code>, pour reprendre la couleur par défaut du
    plan ;</li>
    <li><code>black</code>, <code>red</code>, <code>green</code>,
    <code>yellow</code>, <code>blue</code>, <code>magenta</code>,
    <code>cyan</code> ou <code>white</code>, respectivement pour noir, rouge,
    vert, jaune, bleu, magenta, cyan ou blanc ;</li>
    <li>un numéro entre <code>0</code> et <code>256</code>, correspondant au
    numéro de la couleur dans la palette des 256 couleurs ;</li>
    <li><code>#<em>rrggbb</em></code> où <code><em>rrggbb</em></code> est un
    nombre en hexadécimal correspondant au numéro de la couleur dans la palette
    des 2<sup>64</sup> couleurs.</li>
  </ul>
  <p>Les terminaux manipulent <strong>une</strong> des deux palettes : 8
  couleurs ou 256 couleurs. Chaque couleur est <strong>indexée</strong> à partir
  de 0. Les noms des couleurs sont <strong>transformés</strong> vers leur index
  respectif. Quand une couleur est précisée en hexadécimal, elle est
  <strong>rapportée</strong> à la couleur la plus proche dans la palette
  comportant 256 couleurs.</p>
  <p>Ainsi, si nous voulons écrire <samp>Hello</samp> en jaune sur fond presque
  rouge (<code>#932e2e</code>) et en plus souligné, puis <samp> world</samp>
  mais non-souligné :</p>
  <pre><code class="language-php">Hoa\Console\Cursor::colorize('fg(yellow) bg(#932e2e) underlined');
echo 'Hello';
Hoa\Console\Cursor::colorize('!underlined');
echo ' world';</code></pre>
  <p>Enfin, il est possible de modifier les palettes de couleurs grâce à la
  méthode <code>changeColor</code>, mais c'est à utiliser avec
  <strong>précaution</strong>, cela peut perturber l'utilisateur. Cette méthode
  prend en premier argument l'index de la couleur et en second argument sa
  valeur en hexadécimal. Par exemple, <code>fg(yellow)</code> correspond à
  l'index <code>33</code>, et nous voulons que ce soit maintenant totalement
  bleu :</p>
  <pre><code class="language-php">Hoa\Console\Cursor::changeColor(33, 0xf00);</code></pre>
  <p>Toutefois, la palette de 256 couleurs est suffisamment
  <strong>complète</strong> pour ne pas avoir besoin de modifier les
  couleurs.</p>

  <h3 id="Style" for="main-toc">Style</h3>

  <p>Le curseur n'est pas forcément toujours visible. Lors de certaines
  opérations, nous pouvons le <strong>cacher</strong>, effectuer nos
  déplacements, puis le rendre à nouveau <strong>visible</strong>. Les méthodes
  <code>hide</code> et <code>show</code>, toujours sur
  <code>Hoa\Console\Cursor</code>, sont là pour ça :</p>
  <pre><code class="language-php">echo 'Visible', "\n";
sleep(5);

echo 'Invisible', "\n";
Hoa\Console\Cursor::hide();
sleep(5);

echo 'Visible', "\n";
Hoa\Console\Cursor::show();
sleep(5);</code></pre>
  <p>Il existe aussi trois <strong>types</strong> de curseurs, que nous pouvons
  choisir avec la méthode <code>setStyle</code> :</p>
  <ul>
    <li><code>b[lock]</code> ou <code>▋</code>, pour un curseur en forme de
    bloc ;</li>
    <li><code>u[nderline]</code> ou <code>_</code>, pour un curseur en forme de
    trait de soulignement ;</li>
    <li><code>v[ertical]</code> ou <code>|</code>, pour un curseur en forme de
    barre vertical.</li>
  </ul>
  <p>Cette méthode prend en second argument un booléen indiquant si le curseur
  doit <strong>clignoter</strong> (valeur par défaut) ou pas. Ainsi, nous allons
  faire tous les styles :</p>
  <pre><code class="language-php">echo 'Block/steady: ';
Hoa\Console\Cursor::setStyle('▋', false);
sleep(3);

echo "\n", 'Vertical/blink: ';
Hoa\Console\Cursor::setStyle('|', true);
sleep(3);

// etc.</code></pre>
  <p>Souvent le curseur indique des <strong>zones</strong> ou éléments
  d'<strong>interactions</strong> différents, comme le pointeur de la
  souris.</p>

  <h3 id="Sound" for="main-toc">Son</h3>

  <p>Le curseur est aussi capable d'émettre un petit « bip », souvent pour
  <strong>attirer</strong> l'attention de l'utilisateur. Nous allons utiliser la
  méthode éponyme <code>bip</code> :</p>
  <pre><code class="language-php">Hoa\Console\Cursor::bip();</code></pre>
  <p>Il n'y a qu'une seule <strong>tonalité</strong> disponible.</p>

  <h2 id="Readline" for="main-toc">Lecture en ligne</h2>

  <p>Une manière d'<strong>interagir</strong> avec les utilisateurs est de lire
  le flux <code>STDIN</code>, à savoir le flux d'entrée. Cette
  <strong>lecture</strong> est par défaut très basique : impossible d'effacer,
  impossible d'utiliser les flèches, impossible d'utiliser des raccourcis etc.
  C'est pourquoi il existe la « lecture en ligne », ou
  <em lang="en">readline</em> en anglais, qui reste une lecture sur le flux
  <code>STDIN</code>, mais plus <strong>évoluée</strong>. La bibliothèque
  <code>Hoa\Console\Readline\Readline</code> propose plusieurs fonctionnalités
  que nous allons décrire.</p>

  <h3 id="Basic_usage" for="main-toc">Usage basique</h3>

  <p>Pour <strong>lire une ligne</strong> (c'est à dire une entrée de
  l'utilisateur), nous allons instancier la classe
  <code>Hoa\Console\Readline\Readline</code> et appeler dessus la méthode
  <code>readLine</code>. Chaque appel de cette méthode va attendre que
  l'utilisateur <strong>saisisse</strong> une donnée puis appuye sur
  <kbd title="Enter">↵</kbd>. À ce moment là, la méthode retournera la saisie de
  l'utilisateur (ou <code>false</code> s'il n'y a plus rien à lire). Cette
  méthode prend aussi en argument un <strong>préfixe</strong>, c'est à dire une
  donnée à afficher avant la saisie de la ligne. Il arrive que le terme
  <em>prompt</em> soit aussi utilisé dans la littérature, les deux notions sont
  identiques.</p>
  <p>Ainsi, nous allons écrire un programme qui va lire les entrées de
  l'utilisateur et faire un écho. Le programme terminera si l'utilisateur saisit
  <samp>quit</samp> :</p>
  <pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();

do {
    $line = $rl->readLine('> ');
    echo '&amp;lt; ', $line, "\n\n";
} while (false !== $line &amp;amp;&amp;amp; 'quit' !== $line);</code></pre>
  <p>Maintenant, détaillons les services que nous offre
  <code>Hoa\Console\Readline\Readline</code>.</p>
  <p>Nous sommes capables de nous <strong>déplacer</strong> (comprendre,
  déplacer le curseur) dans la ligne à l'aide des touches <kbd>←</kbd> et
  <kbd>→</kbd>. Nous pouvons à tout moment <strong>effacer</strong> un caractère
  en arrière avec la touche <kbd title="Backspace">⌫</kbd> ou tous les
  caractères jusqu'au début du mot avec <kbd>Ctrl</kbd> + <kbd>W</kbd> (où
  <kbd>W</kbd> signifie <em lang="en">word</em>). Nous pouvons également nous
  déplacer avec des <strong>raccourcis</strong> claviers communs à beaucoup de
  logiciels :</p>
  <ul>
    <li><kbd>Ctrl</kbd> + <kbd>A</kbd>, pour se déplacer en début de
    ligne ;</li>
    <li><kbd>Ctrl</kbd> + <kbd>E</kbd>, pour se déplacer en fin de ligne ;</li>
    <li><kbd>Ctrl</kbd> + <kbd>B</kbd>, pour se déplacer au début du mot courant
    (<kbd>B</kbd> pour <em lang="en">backward</em>) ;</li>
    <li><kbd>Ctrl</kbd> + <kbd>F</kbd>, pour se déplacer en fin du mot courant
    (<kbd>F</kbd> pour <em lang="en">forward</em>).</li>
  </ul>
  <p>Nous avons aussi accès à l'<strong>historique</strong> lorsque nous
  appuyons sur les touches <kbd>↑</kbd> et <kbd>↓</kbd>, respectivement pour
  chercher en arrière et avant dans l'historique. La touche
  <kbd title="Tabulation">⇥</kbd> déclenche l'<strong>auto-complétion</strong>
  si elle est définie. Et enfin, la touche <kbd title="Enter">↵</kbd> retourne
  la saisie.</p>
  <p>Il existe aussi la classe <code>Hoa\Console\Readline\Password</code> qui
  permet d'avoir un lecteur de lignes avec exactement les mêmes services mais
  les caractères <strong>ne s'impriment pas</strong> à l'écran, très utile pour
  lire un <strong>mot de passe</strong> :</p>
  <pre><code class="language-php">$rl  = new Hoa\Console\Readline\Password();
$pwd = $rl->readLine('Password: ');

echo 'Your password is: ', $pwd, "\n";</code></pre>

  <h3 id="Shortcuts" for="main-toc">Raccourcis</h3>

  <p>Pour comprendre comment créer des raccourcis, il faut un tout petit peu
  comprendre le fonctionnement <strong>interne</strong> de
  <code>Hoa\Console\Readline\Readline</code>, et il est très simple. À chaque
  fois que nous appuyons sur une ou plusieurs touches, une
  <strong>chaîne</strong> de caractères représentant cette
  <strong>combinaison</strong> est reçue par notre lecteur. Il regarde si une
  action est associée à cette chaîne : si oui, il l'exécute, si non, il en
  utilise une par défaut qui consiste à afficher la chaîne telle quelle. Chaque
  action retourne un <strong>état</strong> pour le lecteur (qui sont des
  constantes sur <code>Hoa\Console\Readline\Readline</code>) :</p>
  <ul>
    <li><code>STATE_CONTINUE</code>, pour continuer la lecture ;</li>
    <li><code>STATE_BREAK</code>, pour arrêter la lecture ;</li>
    <li><code>STATE_NO_ECHO</code>, pour ne pas afficher la lecture.</li>
  </ul>
  <p>Ainsi, si une action retourne <code class="language-php">STATE_CONTINUE |
  STATE_NO_ECHO</code>, la lecture continuera mais la chaîne qui vient d'être
  reçue ne sera pas affichée. Autre exemple, l'action associée à la touche
  <kbd title="Enter">↵</kbd> retourne l'état <code>STATE_BREAK</code>.</p>
  <p>Pour <strong>ajouter</strong> des actions, nous utilisons la méthode
  <code>addMapping</code>. Elle facilite l'ajout grâce à une syntaxe
  dédiée :</p>
  <ul>
    <li><code>\e[<em>…</em></code>, pour les séquences commençant par le
    caractère <kbd>Esc</kbd> ;</li>
    <li><code>\C-<em>…</em></code>, pour les séquences commençant par le
    caractère <kbd>Ctrl</kbd> ;</li>
    <li><code><em>x</em></code>, n'importe quel caractère.</li>
  </ul>
  <p>Par exemple, si nous voulons afficher <code>z</code> à la place de
  <code>a</code>, nous écrirons :</p>
  <pre><code class="language-php">$rl->addMapping('a', 'z');</code></pre>
  <p>Plus compliqué maintenant, nous pouvons utiliser un
  <em lang="en">callable</em> en second paramètre de
  <code>addMapping</code>. Ce <em lang="en">callable</em> va recevoir l'instance
  de <code>Hoa\Console\Readline\Readline</code> en seul argument. Plusieurs
  méthodes sont là pour aider à <strong>manipuler</strong> le lecteur (gestion
  de l'historique, de la ligne etc.). Par exemple, à chaque fois que nous
  appuyerons sur <kbd>Ctrl</kbd> + <kbd>R</kbd>, nous inverserons la casse de la
  ligne :</p>
  <pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();

// Add mapping.
$rl->addMapping('\C-R', function (Hoa\Console\Readline\Readline $self) {
    // Clear the line.
    Hoa\Console\Cursor::clear('↔');
    echo $self->getPrefix();

    // Get the line text.
    $line = $self->getLine();

    // New line.
    $new  = null;

    // Loop over all characters.
    for ($i = 0, $max = $self->getLineLength(); $i &amp;lt; $max; ++$i) {
        $char = mb_substr($line, $i, 1);

        if ($char === $lower = mb_strtolower($char)) {
            $new .= mb_strtoupper($char);
        } else {
            $new .= $lower;
        }
    }

    // Set the new line.
    $self->setLine($new);

    // Set the buffer (and let the readline echoes or not).
    $self->setBuffer($new);

    // The readline will continue to read.
    return $self::STATE_CONTINUE;
});

// Try!
var_dump($rl->readLine('> '));</code></pre>
  <p>Il ne faut pas hésiter à regarder comment sont implémentés les raccourcis
  précédemment énoncés pour se donner des idées.</p>

  <h3 id="Auto-completion" for="main-toc">Auto-complétion</h3>

  <p>Un outil également très utile lorsque nous écrivons un lecteur de lignes
  est l'<strong>auto-complétion</strong>. Elle se déclenche en appuyant sur la
  touche <kbd title="Tabulation">⇥</kbd> si un auto-compléteur a été défini à
  l'aide de la méthode <code>setAutocompleter</code>.</p>
  <p>Tous les auto-compléteurs doivent implémenter l'interface
  <code>Hoa\Console\Readline\Autocompleter\Autocompleter</code>. Quelqu'uns sont
  déjà présents pour nous <strong>aider</strong> dans notre développement, comme
  <code>Hoa\Console\Readline\Autocompleter\Word</code> qui va auto-compléter la
  saisie à partir d'une <strong>liste de mots</strong>. Par exemple :</p>
  <pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
$rl->setAutocompleter(new Hoa\Console\Readline\Autocompleter\Word([
    'hoa',
    'console',
    'readline',
    'autocompleter',
    'autocompletion',
    'password',
    'awesome'
]));
var_dump($rl->readLine('> '));</code></pre>
  <p>Essayons d'écrire ce que nous voulons, puis où nous le souhaitons, appuyons
  sur <kbd title="Tabulation">⇥</kbd>. Si le texte à gauche du curseur commence
  par <code>h</code>, alors nous verrons <samp>hoa</samp> s'écrire <strong>d'un
  coup</strong> car l'auto-compléteur n'a pas de choix (il retourne une chaîne).
  Si l'auto-compléteur ne trouve aucun mot adapté, il ne se passera
  <strong>rien</strong> (il retournera <code>null</code>).  Et enfin, s'il
  trouve <strong>plusieurs mots</strong> (il retournera un tableau), alors un
  <strong>menu</strong> s'affichera. Essayons d'auto-compléter simplement
  <code>a</code> : le menu proposera <code>autocompleter</code>,
  <samp>autocompletion</samp> et <samp>awesome</samp>. Soit nous continuons à
  taper et le menu va <strong>disparaître</strong>, soit nous pouvons nous
  <strong>déplacer</strong> dans le menu avec les touches
  <kbd title="Tabulation">⇥</kbd>, <kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd> et
  <kbd>←</kbd>, puis <kbd title="Enter">↵</kbd> pour
  <strong>sélectionner</strong> un mot. Le comportement est assez
  <strong>naturel</strong>.</p>
  <p>En plus de l'auto-compléteur sur les mots, nous trouvons un auto-compléteur
  sur les <strong>chemins</strong> avec la classe
  <code>Hoa\Console\Readline\Autocompleter\Path</code>. À partir d'une racine et
  d'un itérateur de fichiers, il est capable d'auto-compléter des chemins. Si la
  racine n'est pas précisée, le dossier courant sera utilisé. À chaque
  auto-complétion, une nouvelle instance de l'itérateur de fichiers est créée
  par une <em lang="en">factory</em>. Elle reçoit en seul argument le chemin à
  itérer. La <em lang="en">factory</em> par défaut est définie par la méthode
  statique <code>getDefaultIteratorFactory</code> sur
  <code>Hoa\Console\Readline\Autocompleter\Path</code>. Elle construit un
  itérateur de fichiers de type
  <a href="http://php.net/directoryiterator"><code>DirectoryIterator</code></a>.
  Chaque valeur calculée par l'itérateur doit être un objet de type
  <a href="http://php.net/splfileinfo"><code>SplFileInfo</code></a>. Ainsi, pour
  auto-compléter tous les fichiers et dossiers à partir de la racine
  <a href="@central_resource:path=Library/Console"><code>hoa://Library/Console</code></a>,
  nous écrirons :</p>
  <pre><code class="language-php">$rl->setAutocompleter(
    new Hoa\Console\Readline\Autocompleter\Path(
        resolve('hoa://Library/Console')
    )
);</code></pre>
  <p>Utiliser une <em lang="en">factory</em> nous offre beaucoup de
  <strong>souplesse</strong> et nous permet d'utiliser n'importe quel itérateur
  de fichiers, comme par exemple <code>Hoa\File\Finder</code> (voir
  <a href="@hack:chapter=File">la bibliothèque <code>Hoa\File</code></a>).
  Ainsi, pour n'auto-compléter que les fichiers et dossiers non cachés qui ont
  été modifiés les 6 derniers mois triés par leur taille, nous écrirons :</p>
  <pre><code class="language-php">$rl->setAutocompleter(
    new Hoa\Console\Readline\Autocompleter\Path(
        resolve('hoa://Library/Console'),
        function ($path) {
            $finder = new Hoa\File\Finder();
            $finder->in($path)
                   ->files()
                   ->directories()
                   ->maxDepth(1)
                   ->name('#^(?!\.).#')
                   ->modified('since 6 months')
                   ->sortBySize();

            return $finder;
        }
    )
);</code></pre>
  <p>Nous pouvons remplacer l'itérateur de fichiers locaux par un itérateur
  totalement <strong>différent</strong> : sur des fichiers stockés sur une autre
  machine, un service tiers ou même des ressources qui ne sont pas des fichiers
  mais ont des URI de la forme d'un chemin.</p>
  <p>Enfin, nous pouvons assembler plusieurs auto-compléteurs entre eux grâce à
  la classe <code>Hoa\Console\Readline\Autocompleter\Aggregate</code>. L'ordre
  de déclaration des auto-compléteurs est important : le premier qui reconnaît
  un mot à auto-compléter prendra la main. Ainsi, pour auto-compléter des
  chemins et des mots, nous écrirons :</p>
  <pre><code class="language-php">$rl->setAutocompleter(
    new Hoa\Console\Readline\Autocompleter\Aggregate([
        new Hoa\Console\Readline\Autocompleter\Path(),
        new Hoa\Console\Readline\Autocompleter\Word($words)
    ])
);
</code></pre>
  <p>La méthode <code>getAutocompleters</code> de
  <code>Hoa\Console\Readline\Autocompleter\Aggregate</code> retourne un objet
  <a href="http://php.net/arrayobject"><code>ArrayObject</code></a> pour plus de
  souplesse. Nous pouvons ainsi toujours ajouter ou supprimer des
  auto-compléteurs après les avoir déclarés dans le constructeur.</p>
  <figure>
    <img src="https://central.hoa-project.net/Resource/Library/Console/Documentation/Image/Readline_autocompleters.gif?format=raw" />
    <figcaption>Exemple d'une agrégation de l'auto-compléteur
    <code>Hoa\Console\Readline\Autocompleter\Path</code> avec
    <code>Hoa\Console\Readline\Autocompleter\Word</code>.</figcaption>
  </figure>

  <h2 id="Reading_options" for="main-toc">Lecture d'options</h2>

  <p>Une grande force des programmes en ligne de commande est leur
  <strong>flexibilité</strong>.  Ils sont <strong>dédiés</strong> à une seule
  (petite) <strong>tâche</strong> et nous pouvons les paramétrer grâce aux
  <strong>options</strong> qu'ils exposent. La <strong>lecture</strong> de ces
  options doit être simple et rapide car c'est une tâche répétitive et délicate.
  La classe <code>Hoa\Console\Parser</code> et
  <code>Hoa\Console\GetOption</code> fonctionnent en <strong>duo</strong> afin
  de répondre à cette problématique.</p>

  <h3 id="Analyzing_options" for="main-toc">Analyser les options</h3>

  <p>Nous allons commencer par utiliser <code>Hoa\Console\Parser</code> qui
  permet d'<strong>analyser</strong> les options données à un programme. Peu
  importe les options que nous voulons précisément, nous nous contentons de les
  analyser pour l'instant. Commençons par utiliser la méthode
  <code>parse</code> :</p>
  <pre><code class="language-php">$parser = new Hoa\Console\Parser();
$parser->parse('-s --long=value input');

print_r($parser->getSwitches());
print_r($parser->getInputs());

/**
 * Will output:
 *     Array
 *     (
 *         [s] => 1
 *         [long] => value
 *     )
 *     Array
 *     (
 *         [0] => input
 *     )
 */</code></pre>
  <p>Étudions un peu de quoi est constituée une ligne de commande. Nous avons
  deux catégories : les <strong>options</strong> (<em lang="en">switches</em>)
  et les <strong>entrées</strong> (<em lang="en">inputs</em>). Les entrées sont
  tout ce qui n'est pas une option. Une option peut avoir deux formes :
  <strong>courte</strong> si elle n'a qu'un seul caractère ou
  <strong>longue</strong> si elle en a plusieurs.</p>
  <p>Ainsi, <code>-s</code> est une option courte, et <code>--long</code> est
  une option longue. Toutefois, il faut aussi considérer le nombre de tirets
  devant l'option : avec deux tirets, ce sera toujours une option longue, avec
  un seul tiret, ça dépend. Il y a deux écoles qui se différencient avec un seul
  <strong>paramètre</strong> : <em lang="en">long only</em>. Prenons un
  exemple : <code>-abc</code> est considéré comme <code>-a -b -c</code> si le
  paramètre <em lang="en">long only</em> est définie à <code>false</code>, sinon
  ce sera équivalent à une option longue, comme <code>--abc</code>.
  Majoritairement, ce paramètre est définie à <code>false</code> par défaut et
  <code>Hoa\Console\Parser</code> s'est rangé du côté de la majorité. Pour
  modifier ce paramètre, il faut utiliser la méthode <code>setLongOnly</code>,
  voyons plutôt :</p>
  <pre><code class="language-php">// long only is set to false.
$parser->parse('-abc');
print_r($parser->getSwitches());

$parser->setLongOnly(true);

// long only is set to true.
$parser->parse('-abc');
print_r($parser->getSwitches());

/**
 * Will output:
 *     Array
 *     (
 *         [a] => 1
 *         [b] => 1
 *         [c] => 1
 *     )
 *     Array
 *     (
 *         [abc] => 1
 *     )
 */</code></pre>
  <p>Une option peut être de deux sortes : <strong>booléenne</strong> ou
  <strong>valuée</strong>. Si aucune valeur ne lui est associée, elle est
  considérée comme booléenne. Ainsi, <code>-s</code> vaut <code>true</code>,
  mais <code>-s -s</code> vaut <code>false</code>, et du coup <code>-s -s
  -s</code> vaut <code>true</code> et ainsi de suite. Une option booléenne
  fonctionne comme un <strong>interrupteur</strong>. Une option valuée a une
  valeur associée, soit par un espace, soit par un signe d'égalité (symbole
  <code>=</code>). Voici une liste non-exhaustive des possibilités avec la
  valeur associée (nous utilisons une option courte mais ça peut être une option
  longue) :</p>
  <ul>
    <li><code>-x=value</code> : <code>value</code> ;</li>
    <li><code>-x=va\ lue</code> : <code>va lue</code> ;</li>
    <li><code>-x="va lue"</code> : <code>va lue</code> ;</li>
    <li><code>-x="va l\"ue"</code> : <code>va l"ue</code> ;</li>
    <li><code>-x value</code> : <code>value</code> ;</li>
    <li><code>-x va\ lue</code> : <code>va lue</code> ;</li>
    <li><code>-x "value"</code> : <code>value</code> ;</li>
    <li><code>-x "va lue"</code> : <code>va lue</code> ;</li>
    <li><code>-x va\ l"ue</code> : <code>va l"ue</code> ;</li>
    <li><code>-x 'va "l"ue'</code> : <code>va "l"ue</code> ;</li>
    <li>etc.</li>
  </ul>
  <p>Les simples (symbole <code>'</code>) et doubles (symbole <code>"</code>)
  guillemets sont supportés. Mais attention, il y a des cas particuliers qui ne
  sont pas toujours <strong>standards</strong> :</p>
  <ul>
    <li><code>-x=-value</code> : <code>-value</code> ;</li>
    <li><code>-x "-value"</code> : <code>-value</code> ;</li>
    <li><code>-x \-value</code> : <code>-value</code> ;</li>
    <li><code>-x -value</code> : équivaut à deux options booléennes
    <code>-x</code> et <code>-value</code> ;</li>
    <li><code>-x=-7</code> : <code>-7</code> ;</li>
    <li>etc.</li>
  </ul>
  <p><em>À l'instar</em> des options booléennes qui fonctionnent comme des
  interrupteurs, les options valuées <strong>réécrivent</strong> leurs valeurs
  si elles sont déclarées plusieurs fois. Ainsi avec <code>-a=b -a=c</code>,
  <code>-a</code> vaudra <code>c</code>.</p>
  <p>Enfin, il y a des valeurs qui sont considérées comme
  <strong>spéciales</strong>. Nous en distingons deux :</p>
  <ul>
    <li>les <strong>listes</strong>, à l'aide de la virgule comme séparateur :
    <code>-x=a,b,c</code> ;</li>
    <li>les <strong>intervalles</strong>, à l'aide du symbole <code>:</code>
    (sans espace autour) : <code>-x=1:7</code>.</li>
  </ul>
  <p>Sans aucune manipulation, ces valeurs ne seront pas considérées comme
  spéciales. Il faudra utiliser la méthode
  <code>Hoa\Console\Parser::parseSpecialValue</code> comme nous allons le voir
  très prochainement.</p>

  <h3 id="Read_options_and_inputs" for="main-toc">Lire les options et les
  entrées</h3>

  <p>Nous savons analyser les options mais ce n'est pas suffisant pour les lire
  correctement. Il faut leur donner une petite <strong>sémantique</strong> :
  qu'attendent-elles, quelle est leur nature etc. Pour cela, nous allons nous
  aider de la classe <code>Hoa\Console\GetOption</code>. Une option est
  caractérisée par :</p>
  <ul>
    <li>un nom <strong>long</strong> ;</li>
    <li>un nom <strong>court</strong> ;</li>
    <li>un <strong>type</strong>, donné par une des constantes de
    <code>Hoa\Console\GetOption</code>, parmi :
      <ul>
        <li><code>NO_ARGUMENT</code> si l'option est booléenne ;</li>
        <li><code>REQUIRED_ARGUMENT</code> si l'option est valuée ;</li>
        <li><code>OPTIONAL_ARGUMENT</code> si l'option peut avoir une
        valeur.</li>
      </ul>
    </li>
  </ul>
  <p>Ces trois informations sont <strong>obligatoires</strong>. Elles doivent
  être données au constructeur de <code>Hoa\Console\GetOption</code> en premier
  argument. Le second argument est l'analyseur d'options (l'analyse doit être
  <strong>préalablement</strong> effectuée). Ainsi nous décrivons deux options :
  <code>extract</code> qui est une option booléenne, et <code>directory</code>
  qui est une option valuée :</p>
  <pre><code class="language-php">$parser = new Hoa\Console\Parser();
$parser->parse('-x --directory=value inputA inputB inputC');

$options = new Hoa\Console\GetOption(
    [
        // long name                  type                  short name
        //   ↓                         ↓                         ↓
        ['extract',   Hoa\Console\GetOption::NO_ARGUMENT,       'x'],
        ['directory', Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'd']
    ],
    $parser
);</code></pre>
  <p>Nous pouvons maintenant lire nos options ! Le lecteur d'options fonctionne
  comme un itérateur, ou plutôt une <strong>pipette</strong>, à l'aide de la
  méthode <code>getOption</code>. Cette méthode retourne le nom court de
  l'option lue et assignera la valeur de l'option (un booléen ou une chaîne de
  caractères) à son premier argument passé en référence. Quand la pipette est
  vide, la méthode <code>getOption</code> retourne <code>false</code>.
  Cette structure peut paraître originale mais elle est pourtant très
  <strong>répandue</strong>, vous ne serez pas déroutés en la voyant autre part
  (exemples
  <a href="http://kernel.org/doc/man-pages/online/pages/man3/getopt.3.html#EXAMPLE"
     title="getopt(3), Linux Programmer's Manual">dans Linux</a>,
  <a href="http://freebsd.org/cgi/man.cgi?query=getopt&amp;sektion=3#EXAMPLES"
     title="getopt(3), FreeBSD Library Functions Manual">dans FreeBSD</a> ou
  <a href="http://developer.apple.com/library/Mac/#documentation/Darwin/Reference/ManPages/man3/getopt.3.html"
     title="getopt(3), BSD Library Functions Manual">dans Mac OS X</a> — même
  base de code —). La manière la plus simple pour lire les options est de
  définir des valeurs par défaut pour nos options, puis d'utiliser
  <code>getOption</code>, ainsi :</p>
  <pre><code class="language-php">$extract   = false;
$directory = '.';

//          short name                  value
//               ↓                        ↓
while (false !== $c = $options->getOption($v)) {
    switch($c) {
        case 'x':
            $extract = $v;

            break;

        case 'd':
            $directory = $v;

            break;
    }
}

var_dump($extract, $directory);

/**
 * Will output:
 *     bool(true)
 *     string(5) "value"
 */</code></pre>
  <p>Cela se lit : « tant que nous avons une option à lire, nous récupérons
  son nom court dans <code>$c</code> et sa valeur dans <code>$v</code>, puis
  nous regardons quoi en faire ».</p>
  <p>Pour lire les entrées, nous utiliserons la méthode
  <code>Hoa\Console\Parser::listInputs</code> dont tous les arguments (au nombre
  de 26) sont passés en <strong>référence</strong>. Ainsi :</p>
  <pre><code class="language-php">$parser->listInputs($inputA, $inputB, $inputC);

var_dump($inputA, $inputB, $inputC);

/**
 * Will output:
 *     string(6) "inputA"
 *     string(6) "inputB"
 *     string(6) "inputC"
 */</code></pre>
  <p>Attention, cette façon de procéder implique que les entrées sont
  <strong>ordonnées</strong> (comme c'est pratiquement toujours le cas). Mais
  aussi, lire les entrées sans avoir préalablement donné l'analyseur à
  <code>Hoa\Console\GetOption</code> peut produire des résultats imprévus (car
  par défaut, toutes les options sont considérées comme booléennes). Si nous
  voulons toutes les entrées et les analyser manuellement si elles ne sont pas
  ordonnées, nous pouvons utiliser la méthode
  <code>Hoa\Console\Parser::getInputs</code> qui retournera toutes les
  entrées.</p>

  <h3 id="Special_or_ambiguous_options" for="main-toc">Options spéciales ou
  ambiguës</h3>

  <p>Revenons sur la méthode <code>Hoa\Console\Parser::parseSpecialValue</code>.
  Elle prend deux arguments : une valeur et un tableau de mots-clés. Voyons
  plutôt. Nous reprenons notre exemple et modifions le cas pour l'option
  <code>d</code> :</p>
  <pre data-line="8-11"><code class="language-php">while (false !== $c = $options->getOption($v)) {
    switch($c) {
        case 'x':
            $extract = $v;

            break;

        case 'd':
            $directory = $parser->parseSpecialValue($v, ['HOME' => '/tmp']);

            break;
    }
}

print_r($directory);</code></pre>
  <p>Si nous essayons avec <code>-d=a,b,HOME,c,d</code>, alors <code>-d</code>
  aura la valeur suivante :</p>
  <pre><code class="language-php">/**
 * Array
 * (
 *     [0] => a
 *     [1] => b
 *     [2] => /tmp
 *     [3] => c
 *     [4] => d
 * )
 */</code></pre>
  <p>Enfin, quand une option lue n'existe pas mais qu'elle est très
  <strong>proche</strong> d'une option existante à quelques
  <strong>fautes</strong> près (par exemple <code>--dirzctory</code> au lieu de
  <code>--directory</code>), nous pouvons utiliser le cas
  <code>__ambiguous</code> pour la capturer et la traiter :</p>
  <pre data-line="13-16"><code class="language-php">while (false !== $c = $options->getOption($v)) {
    switch($c) {
        case 'x':
            $extract = $v;

            break;

        case 'd':
            $directory = $parser->parseSpecialValue($v, ['HOME' => '/tmp']);

            break;

        case '__ambiguous':
            print_r($v);

            break;
    }
}</code></pre>
  <p>La valeur (dans <code>$v</code>) est un tableau avec trois entrées. Par
  exemple avec <code>--dirzctory</code>, nous obtenons :</p>
  <pre><code class="language-php">/**
 * Array
 * (
 *     [solutions] => Array
 *         (
 *             [0] => directory
 *         )
 *
 *     [value] => y
 *     [option] => dirzctory
 * )
 */</code></pre>
  <p>La clé <code>solutions</code> propose toutes les options
  <strong>similaires</strong>, la clé <code>value</code> donne la valeur de
  l'option et <code>option</code> le nom <strong>original</strong> lu. C'est à
  l'utilisateur de décider quoi faire à partir de ces informations. Nous pouvons
  utiliser la méthode <code>Hoa\Console\GetOption::resolveOptionAmbiguity</code>
  en lui donnant ce tableau, et elle choisira la meilleure option si elle existe :</p>
  <pre><code class="language-php">    case '__ambiguous':
        $options->resolveOptionAmbiguity($v);

        break;
</code></pre>
  <p>Il est quand même préférable d'<strong>avertir</strong> l'utilisateur qu'il
  y a une ambiguïté et de lui demander son avis. Il peut parfois être
  <strong>dangereux</strong> de prendre la décision à sa place.</p>

  <h3 id="Integrate_a_router_and_a_dispatcher" for="main-toc">Intégrer un
  routeur et un dispatcheur</h3>

  <p>Jusqu'à maintenant, nous forcions des options et des entrées à l'analyseur.
  <code>Hoa\Router\Cli</code> permet d'<strong>extraire</strong> des données
  depuis un programme en ligne de commande. Une méthode nous intéresse :
  <code>Hoa\Router\Cli::getURI</code>, qui va nous donner toutes les options et
  les entrées du programme courant, que nous pourrons alors
  <strong>fournir</strong> à notre analyseur. Ainsi :</p>
  <pre data-line="2"><code class="language-php">$parser = new Hoa\Console\Parser();
$parser->parse(Hoa\Router\Cli::getURI());

// …</code></pre>
  <p>Il est maintenant possible d'interpréter les options que nous donnons à
  notre propre programme. Si vous avez écrit les tests dans un fichier nommé
  <code>Test.php</code>, alors vous pourrez écrire :</p>
  <pre><code class="language-shell">$ php Test.php -x -d=a,b,HOME,c,d inputA inputB
bool(true)
Array
(
    [0] => a
    [1] => b
    [2] => /tmp
    [3] => c
    [4] => d
)
string(6) "inputA"
string(6) "inputB"
NULL</code></pre>
  <p>L'option <code>-x</code> vaut bien <code>true</code>, l'option
  <code>-d</code> vaut un tableau (car nous l'avons analysé avec la méthode
  <code>Hoa\Console\Parser::parseSpecialValue</code>), et nous avons
  <code>inputA</code>, <code>inputB</code> et <code>null</code> en entrée.</p>
  <p>C'est un bon début, et nous pourrions nous arrêter là dans la plupart des
  cas. Mais il est possible d'aller plus loin en mettant en place un
  <strong>dispatcheur</strong> : écrire des commandes dans plusieurs fonctions
  ou classes et les appeler en fonction des options et entrées données à notre
  programme. Nous vous conseillons de regarder le code source de
  <a href="@central_resource:path=Library/Cli/Bin/Hoa.php"><code>hoa://Library/Cli/Bin/Hoa.php</code></a>
  pour vous aider, ainsi que les chapitres de
  <a href="@hack:chapter=Router"><code>Hoa\Router</code></a> et
  <a href="@hack:chapter=Dispatcher"><code>Hoa\Dispatcher</code></a>. Nous
  proposons un exemple rapide sans donner trop de détails sur les bibliothèques
  précédement citées.</p>
  <p>L'idée est la suivante. Grâce à <code>Hoa\Router\Cli</code>, nous allons
  extraire des données de la forme suivante : <code>$ php script.php
  <em>controller</em> <em>tail</em></code>, où <code><em>controller</em></code>
  sera le nom du contrôleur (d'une classe) sur laquelle nous appellerons
  l'action <code>main</code> (soit la méthode <code>main</code> avec les
  paramètres par défaut), et où <code><em>tail</em></code> correspond aux
  options et aux entrées. Le nom du contrôleur est identifié par la variable
  spéciale <code>_call</code> (au niveau de <code>Hoa\Router\Cli</code>) et les
  options ainsi que les entrées par <code>_tail</code> (au niveau de
  <code>Hoa\Dispatcher\Kit</code>). Les options et entrées ne sont pas
  obligatoires. Ensuite, nous allons utiliser <code>Hoa\Dispatcher\Basic</code>
  avec le kit dédié aux terminaux, à savoir
  <code>Hoa\Console\Dispatcher\Kit</code>. Le dispatcheur va chercher à charger
  les classes <code>Application\Controller\<em>controller</em></code> par
  défaut, et l'auto-chargeur va les chercher dans le dossier
  <code>hoa://Application/Controller/<em>controller</em></code>. Nous allons
  donc préciser où se trouve l'application très rapidement. Enfin, le code de
  retour de notre programme sera donné par la valeur de retour de notre
  contrôleur et de notre action. En cas d'erreur, nous l'afficherons et nous
  forcerons un code de retour supérieur à zéro. Ainsi :</p>
  <pre><code class="language-php">try {
    // Prepare the router.
    $router = new Hoa\Router\Cli();
    $router->get(
        'g',
        '(?&amp;lt;_call>\w+)(?:\s+(?&amp;lt;_tail>.+))?'
    );

    // Prepare the dispatcher.
    $dispatcher = new Hoa\Dispatcher\ClassMethod([
        'synchronous.call' => 'Application\Controller\(:call:U:)',
        'synchronous.able' => 'main'
    ]);
    $dispatcher->setKitName('Hoa\Console\Dispatcher\Kit');

    // Dispatch!
    exit($dispatcher->dispatch($router));
} catch (Hoa\Exception $e) {
    echo $e->raise(true);
    exit($e->getCode() + 1);
}</code></pre>
  <p>Au même niveau que notre programme, créons le dossier
  <code>Application/Controller/</code> avec le fichier <code>Foo.php</code> à
  l'intérieur, qui contiendra le code suivant :</p>
  <pre><code class="language-php">&amp;lt;?php

namespace Application\Controller;

class Foo extends \Hoa\Console\Dispatcher\Kit
{
    protected $options = [
        ['extract',   \Hoa\Console\GetOption::NO_ARGUMENT,       'x'],
        ['directory', \Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'd'],
        ['help',      \Hoa\Console\GetOption::NO_ARGUMENT,       'h']
    ];

    public function MainAction()
    {
        $extract   = false;
        $directory = '.';

        while (false !== $c = $this->getOption($v)) {
            switch($c) {
                case 'x':
                    $extract = $v;

                    break;

                case 'd':
                    $directory = $this->parser->parseSpecialValue($v, ['HOME' => '/tmp']);

                    break;

                case 'h':
                    return $this->usage();
            }
        }

        echo 'extract:   ';
        var_dump($extract);
        echo 'directory: ';
        print_r($directory);

        return;
    }

    public function usage()
    {
        echo
            'Usage   : foo &amp;lt;options>', "\n",
            'Options :', "\n",
            $this->makeUsageOptionsList([
                'x' => 'Whether we need to extract.',
                'd' => 'Directory to extract.',
                'h' => 'This help.'
            ]);
    }
}</code></pre>
  <p>Notre classe étend bien notre kit pour bénéficier des méthodes qu'il
  propose. Entre autre, sa propre méthode <code>getOption</code>, qui va
  exploiter l'attribut <code>$options</code> où sont déclarées les options,
  <code>makeUsageOptionsList</code> pour afficher une aide, sa propre méthode
  <code>resolveOptionAmbiguity</code> qui demande une confirmation à
  l'utilisateur, l'accès au routeur à travers l'attribut <code>$router</code>
  etc. Les kits offrent des <strong>services</strong> à l'application, ils
  <strong>aggrègent</strong> des services offerts par les bibliothèques.
  Maintenant testons :</p>
  <pre><code class="language-shell">$ php Test.php foo -x -d=1:3
extract:   bool(true)
directory: Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)</code></pre>
  <p>Magnifique !</p>
  <p>Précisons que le script <code>hoa</code> est exactement construit de cette
  manière. N'hésitez pas à vous en inspirer.</p>

  <h2 id="Processus" for="main-toc">Processus</h2>

  <p>Dans notre contexte, un <strong>processus</strong> est un programme
  classique qui s'exécute dans un <strong>terminal</strong>. Ce qui est
  intéressant, c'est qu'un tel programme <strong>communique</strong> avec le
  reste de son <strong>environnement</strong> grâce à des
  <strong>tuyaux</strong>, ou <em lang="en">pipes</em> en anglais, numérotés à
  partir de zéro. Certains ont même des noms et sont standards :</p>
  <ul>
    <li><code>STDIN</code> (<code>0</code>) pour lire des
    <strong>entrées</strong> (<em lang="en">standard input</em>) ;</li>
    <li><code>STDOUT</code> (<code>1</code>) pour écrire des
    <strong>sorties</strong> (<em lang="en">standard output</em>) ;</li>
    <li><code>STDERR</code> (<code>2</code>) pour écrire des
    <strong>erreurs</strong> (<em lang="en">standard error</em>).</li>
  </ul>
  <p>Quand un processus s'exécute dans un terminal, <code>STDIN</code> utilise
  le <strong>clavier</strong> comme source de données, et <code>STDOUT</code>
  comme <code>STDERR</code> sont reliés à la <strong>fenêtre</strong> d'un
  terminal. Mais quand un processus est exécuté dans un
  <strong>sous-terminal</strong>, c'est à dire exécuté à partir d'un autre
  processus, <code>STDIN</code> n'est pas relié au clavier, tout comme
  <code>STDOUT</code> et <code>STDERR</code> ne sont pas reliés à l'écran.
  C'est le processus parent qui va écrire et lire sur ces flux pour
  <strong>interagir</strong> avec le « sous »-processus. Ce mécanisme s'appelle
  la <strong>redirection</strong> de flux, nous l'utilisons très souvent quand
  nous écrivons une ligne de commande (voir
  <a href="http://gnu.org/software/bash/manual/bashref.html#Redirections">section
  <em lang="en">Redirections</em> du <em lang="en">Bash Reference
  Manual</em></a>). Ce que nous allons faire utilise une autre syntaxe mais le
  mécanisme est le même.</p>
  <p>Il est très important de savoir que ces flux sont tous
  <strong>asynchrones</strong> les uns par rapport aux autres. Aucun flux n'a
  un impact sur un autre, il n'y a aucun lien entre eux et c'est important pour
  la suite.</p>
  <p>Au niveau de PHP, il est possible d'accéder à ces flux en utilisant
  respectivement les URI suivants : <code>php://stdin</code>,
  <code>php://stdout</code> et <code>php://stderr</code>. Mais nous avons aussi
  les constantes éponymes <code>STDIN</code>, <code>STDOUT</code> et
  <code>STDERR</code>. Elles sont définies comme suit (exemple avec
  <code>STDIN</code>) :</p>
  <pre><code class="language-php">define('STDIN', fopen('php://stdin', 'r'));</code></pre>
  <p>Ces flux ne sont disponibles que si le programme s'exécute en ligne de
  commande. Rappelons-nous également que les <em lang="en">pipes</em> sont
  identifiés par des numéros. Nous pouvons alors utiliser
  <code>php://fd/0</code> pour se référer à <code>STDIN</code>,
  <code>php://fd/1</code> pour <code>STDOUT</code> etc. L'URI
  <code>php://fd/<em>i</em></code> permet d'accéder au fichier ayant le
  <strong>descripteur</strong> <code><em>i</em></code>.</p>

  <h3 id="Very_basic_execution" for="main-toc">Exécution très basique</h3>

  <p>La classe <code>Hoa\Console\Processus</code> propose une manière très
  <strong>rapide</strong> d'exécuter un processus et d'obtenir le résultat de
  <code>STDOUT</code>. C'est le cas le plus commun. Ainsi, nous allons utiliser
  la méthode statique <code>execute</code> :</p>
  <pre><code class="language-php">var_dump(Hoa\Console\Processus::execute('id -u -n'));

/**
 * Could output:
 *     string(3) "hoa"
 */</code></pre>
  <p>Par défaut, la commande sera échappée pour des raisons de sécurité. Si vous
  avez confiance dans la commande, vous pouvez désactiver l'échappement en
  passant <code>false</code> en second argument.</p>
  <p>Nous n'avons aucun contrôle sur les <em lang="en">pipes</em> et même si ça
  convient dans la plupart des cas, ce n'est pas suffisant quand nous souhaitons
  un minimum d'interaction avec le processus.</p>

  <h3 id="Reading_and_writing" for="main-toc">Lecture et écriture</h3>

  <p>Voyons comment <strong>interagir</strong> avec un processus. Nous allons
  considérer le programme <code>LittleProcessus.php</code> suivant :</p>
  <pre><code class="language-php">&amp;lt;?php

$range = range('a', 'z');

while (false !== $line = fgets(STDIN)) {
    echo '> ', $range[intval($line)], "\n";
}</code></pre>
  <p>Pour tester et comprendre son fonctionnement, écrivons la ligne de commande
  suivante et entrons au clavier <code>3</code>, puis <code>4</code> :</p>
  <pre><code class="language-shell">$ php LittleProcessus.php
3
> d
4
> e
</code></pre>
  <p>Nous pouvons aussi écrire :</p>
  <pre><code class="language-shell">$ seq 0 4 | php LittleProcessus.php
> a
> b
> c
> d
> e</code></pre>
  <p>Notre programme va lire chaque ligne sur l'entrée standard, considérer que
  c'est un nombre, et le transformer en caractère qui sera affiché sur la sortie
  standard. Nous aimerions exécuter ce programme en lui donnant nous-même une
  liste de nombres (comme le programme <code>seq</code>) et en observant le
  résultat qu'il produira.</p>
  <p>Une instance de la classe <code>Hoa\Console\Processus</code> représente un
  <strong>processus</strong>. Lors de l'instanciation, nous devons
  préciser :</p>
  <ul>
    <li>le <strong>nom</strong> du processus ;</li>
    <li>ses <strong>options</strong> ;</li>
    <li>la <strong>description</strong> des <em lang="en">pipes</em>.</li>
  </ul>
  <p>Il y a d'autres arguments mais nous les verrons plus tard.</p>
  <p>La description des <em lang="en">pipes</em> a la forme d'un tableau où
  chaque clé représente le numéro du <em lang="en">pipe</em> (plus généralement,
  c'est le <code><em>i</em></code> de <code>php://fd/<em>i</em></code>) et la
  valeur est encore un tableau décrivant la nature du <em lang="en">pipe</em>,
  soit un « vrai » <em lang="en">pipe</em>, soit un fichier, avec leur mode de
  lecture ou d'écriture (parmi <code>r</code>, <code>w</code> ou
  <code>a</code>). Illustrons avec un exemple :</p>
  <pre><code class="language-php">$processus = new Hoa\Console\Processus(
    'php',
    ['LittleProcessus.php'],
    [
        // STDIN.
        0 => ['pipe', 'r'],
        // STDOUT.
        1 => ['file', '/tmp/output', 'a']
    ]
);</code></pre>
  <p>Dans ce cas, <code>STDIN</code> est un <em lang="en">pipe</em> et
  <code>STDOUT</code> est le fichier <code>/tmp/output</code>. Si nous ne
  précisions pas de descripteur, ce sera équivalent à écrire :</p>
  <pre><code class="language-php">$processus = new Hoa\Console\Processus(
    'php',
    ['LittleProcessus.php'],
    [
        // STDIN.
        0 => ['pipe', 'r'],
        // STDOUT.
        1 => ['pipe', 'w'],
        // STDERR.
        2 => ['pipe', 'w']
    ]
);</code></pre>
  <p>Chaque <em lang="en">pipe</em> est reconnu comme un <strong>flux</strong>
  et peut être manipulé comme tel. Quand un <em lang="en">pipe</em> est en
  <strong>lecture</strong> (avec le mode <code>r</code>), cela signifie que le
  processus va <strong>lire</strong> dessus. Donc nous, le processus parent,
  nous allons <strong>écrire</strong> sur ce <em lang="en">pipe</em>.  Prenons
  l'exemple de <code>STDIN</code> : le processus lit sur <code>STDIN</code> ce
  que le clavier a écrit dessus. Et inversement, quand un
  <em lang="en">pipe</em> est en <strong>écriture</strong> (avec le mode
  <code>w</code>), cela signifie que nous allons <strong>lire</strong> dessus.
  Prenons l'exemple de <code>STDOUT</code> : l'écran va lire ce que le processus
  lui a écrit.</p>
  <p>La classe <code>Hoa\Console\Processus</code> étend la classe
  <a href="@hack:chapter=Stream"><code>Hoa\Stream</code></a>, et de ce fait,
  nous avons tous les outils nécessaires pour lire et écrire sur les
  <em lang="en">pipes</em> de notre choix. Cette classe propose aussi plusieurs
  <strong>écouteurs</strong> :</p>
  <ul>
    <li><code>start</code>, quand le processus est démarré ;</li>
    <li><code>stop</code>, quand le processus est arrêté ;</li>
    <li><code>input</code>, quand les flux en lecture sont prêts ;</li>
    <li><code>output</code>, quand les flux en écriture sont prêts ;</li>
    <li><code>timeout</code>, quand le processus s'exécute depuis trop
    longtemps.</li>
  </ul>
  <p>Prenons directement un exemple. Nous allons exécuter le processus
  <code>php LittleProcessus.php</code> et attacher des fonctions aux écouteurs
  suivants : <code>input</code> pour écrire une série de chiffres et
  <code>output</code> pour lire le résultat.</p>
  <pre><code class="language-php">$processus = new Hoa\Console\Processus('php LittleProcessus.php');
$processus->on('input', function ($bucket) {
    $source = $bucket->getSource();
    $data   = $bucket->getData();

    echo 'INPUT (', $data['pipe'], ')', "\n";

    $source->writeAll(
        implode("\n", range($i = mt_rand(0, 21), $i + 4)) . "\n"
    );

    return false;
});
$processus->on('output', function ($bucket) {
    $data = $bucket->getData();

    echo 'OUTPUT (', $data['pipe'], ') ', $data['line'], "\n";

    return;
});
$processus->run();

/**
 * Could output:
 *     INPUT (0)
 *     OUTPUT (1) > s
 *     OUTPUT (1) > t
 *     OUTPUT (1) > u
 *     OUTPUT (1) > v
 *     OUTPUT (1) > w
 */</code></pre>
  <p>Maintenant, rentrons dans le détail pour bien comprendre les choses.</p>
  <p>Quand un flux en <strong>lecture</strong> est <strong>prêt</strong>, alors
  l'écouteur <code>input</code> se déclenche. Une seule donnée est envoyée :
  <code>pipe</code> qui contient le numéro du <em lang="en">pipe</em> (le
  <code><em>i</em></code> de <code>php://fd/<em>i</em></code>). Quand un flux en
  <strong>écriture</strong> est prêt, alors l'écouteur <code>output</code> se
  déclenche. Deux données sont envoyées : <code>pipe</code> (comme pour
  <code>input</code>) et <code>line</code> qui est la <strong>ligne
  reçue</strong>.</p>
  <p>Nous voyons dans la fonction attachée à l'écouteur <code>input</code> que
  nous écrivons une suite de nombres concaténés par <code>\n</code> (un nombre
  par ligne). Pour cela, nous utilisons la méthode <code>writeAll</code>. Par
  défaut, les méthodes d'écriture écrivent sur le <em lang="en">pipe</em>
  <code>0</code>. Pour changer ce comportement, il faudra donner le numéro de
  <em lang="en">pipe</em> en second argument des méthodes d'écriture. Pareil
  pour les méthodes de lecture mais le <em lang="en">pipe</em> par défaut est
  <code>1</code>.</p>
  <p>Quand un <em lang="en">callable</em> attaché à un écouteur retourne
  <code>false</code>, le <em lang="en">pipe</em> qui a déclenché cet appel sera
  <strong>fermé</strong> juste après. Dans notre cas, la fonction attachée à
  <code>input</code> retourne <code>false</code> juste après avoir écrit, nous
  n'avons plus besoin de ce <em lang="en">pipe</em>. Il est important pour des
  raisons de <strong>performances</strong> de fermer les
  <em lang="en">pipes</em> dès que possible.</p>
  <p>Enfin, pour <strong>exécuter</strong> le processus, nous utilisons la
  méthode <code>Hoa\Console\Processus::run</code> d'arité nulle.</p>
  <p>Dans notre exemple, nous écrivons toutes les données d'un coup  mais nous
  pouvons envoyer les données dès qu'elles sont disponibles, ce qui est plus
  performant car le processus n'attend pas un gros paquet de données : il peut
  les traiter au fur et à mesure. Modifions notre exemple pour écrire une donnée
  à chaque fois que <code>STDIN</code> est prêt :</p>
  <pre><code class="language-php">$processus->on('input', function ($bucket) {
    static $i = null;
    static $j = 5;

    if (null === $i) {
        $i = mt_rand(0, 20);
    }

    $data = $bucket->getData();

    echo 'INPUT (', $data['pipe'],')', "\n";

    $source = $bucket->getSource();
    $source->writeLine($i++);
    usleep(50000);

    if (0 >= $j--) {
        return false;
    }

    return;
});</code></pre>
  <p>Nous initialisons deux variables : <code class="language-php">$i</code> et
  <code class="language-php">$j</code>, qui portent le nombre à envoyer et le
  nombre maximum de données à envoyer. Nous introduisons une latence volontaire
  avec <code class="language-php">usleep(50000)</code> pour laisser le temps à
  <code>STDOUT</code> d'être prêt, ceci afin de mieux illustrer notre exemple.
  Dans ce cas, la sortie serait :</p>
  <pre><code class="language-php">/** Could output:
 *     INPUT (0)
 *     OUTPUT (1) > h
 *     INPUT (0)
 *     OUTPUT (1) > i
 *     INPUT (0)
 *     OUTPUT (1) > j
 *     INPUT (0)
 *     OUTPUT (1) > k
 *     INPUT (0)
 *     OUTPUT (1) > l
 *     INPUT (0)
 *     OUTPUT (1) > m
 */</code></pre>
  <p>Le processus est en attente d'une entrée et lit les données dès qu'elles
  arrivent. Une fois que nous avons envoyé toutes les données, nous fermons le
  <em lang="en">pipe</em>.</p>
  <p>Le processus se <strong>ferme</strong> de lui-même. Nous avons la méthode
  <code>Hoa\Console\Processus::getExitCode</code> pour connaître le
  <strong>code</strong> de retour du processus. Attention, un code
  <code>0</code> représente un <strong>succès</strong>.  Comme l'erreur est
  répandue, il existe la méthode
  <code>Hoa\Console\Processus::isSuccessful</code> pour savoir si le processus
  s'est exécuté avec succès ou pas.</p>

  <h3 id="Detect_the_type_of_pipes" for="main-toc">Détecter le type des
  <em lang="en">pipes</em></h3>

  <p>Parfois, il est utile de connaître le <strong>type</strong> des
  <em lang="en">pipes</em>, c'est à dire si c'est une utilisation
  <strong>directe</strong>, un <strong><em lang="en">pipe</em></strong> ou une
  <strong>redirection</strong>. Nous allons nous aider de la classe
  <code>Hoa\Console\Console</code> et de ses méthodes statiques
  <code>isDirect</code>, <code>isPipe</code> et <code>isRedirection</code> pour
  obtenir ces informations.</p>
  <p>Prenons un exemple pour comprendre plus rapidement. Écrivons le fichier
  <code>Type.php</code> qui va étudier le type de <code>STDOUT</code> :</p>
  <pre><code class="language-php">echo 'is direct:      ';
var_dump(Hoa\Console\Console::isDirect(STDOUT));

echo 'is pipe:        ';
var_dump(Hoa\Console\Console::isPipe(STDOUT));

echo 'is redirection: ';
var_dump(Hoa\Console\Console::isRedirection(STDOUT));</code></pre>
  <p>Et maintenant, exécutons ce fichier pour voir le résultat :</p>
  <pre><code class="language-shell">$ php Type.php
is direct:      bool(true)
is pipe:        bool(false)
is redirection: bool(false)

$ php Type.php | xargs -I@ echo @
is direct:      bool(false)
is pipe:        bool(true)
is redirection: bool(false)

$ php Type.php > /tmp/foo; cat /tmp/foo
is direct:      bool(false)
is pipe:        bool(false)
is redirection: bool(true)</code></pre>
  <p>Dans le premier cas, <code>STDOUT</code> est bien <strong>direct</strong>
  (pour <code>STDOUT</code>, cela signifie qu'il est <strong>relié</strong> à
  l'écran, pour <code>STDIN</code>, il serait relié au clavier etc.). Dans le
  deuxième cas, <code>STDOUT</code> est un
  <strong><em lang="en">pipe</em></strong>, c'est à dire qu'il est
  <strong>attaché</strong> au <code>STDIN</code> de la commande située après le
  symbole <code>|</code>. Dans le dernier cas, <code>STDOUT</code> est une
  <strong>redirection</strong>, c'est à dire qu'il est <strong>redirigé</strong>
  dans le fichier <code>/tmp/foo</code> (que nous affichons juste après).
  L'opération peut se faire sur <code>STDIN</code>, <code>STDERR</code> ou
  n'importe quelle autre ressource.</p>
  <p>Connaître le type des <em lang="en">pipes</em> peut permettre des
  comportements différents selon le <strong>contexte</strong>. Par exemple,
  <code>Hoa\Console\Readline\Readline</code> lit sur <code>STDIN</code>. Si son
  type est un <em lang="en">pipe</em> ou une redirection, le mode d'édition de
  ligne avancé sera désactivé et il retourne <code>false</code> quand il n'a
  plus rien à lire. Autre exemple, la verbosité des commandes du script
  <code>hoa</code> utilise le type de <code>STDOUT</code> comme valeur par
  défaut : direct pour être verbeux, sinon non-verbeux. Essayez les exemples
  suivants pour voir la différence :</p>
  <pre><code class="language-shell">$ hoa --no-verbose
$ hoa | xargs -I@ echo @</code></pre>
  <p>Les exemples ne manquent pas mais attention à utiliser cette fonctionnalité
  avec intelligence. Il faut adapter les comportements mais rester
  <strong>cohérent</strong>.</p>

  <h3 id="Execution_conditions" for="main-toc">Condition d'exécution</h3>

  <p>Le processus s'exécute dans un <strong>dossier</strong> particulier et un
  <strong>environnement</strong> particulier. Le dossier est appelé
  <em lang="en">current working directory</em>, souvent abrégé
  <abbr lang="en">cwd</abbr>. Il définit le dossier où sera exécuté le
  processus. Nous pouvons le retrouver en PHP avec
  <a href="http://php.net/getcwd">la fonction <code>getcwd</code></a>.
  L'environnement se définit par un tableau que nous retrouvons par exemple en
  exécutant <code>/usr/bin/env</code>. C'est dans cet environnement qu'est
  présent le <code>PATH</code> par exemple. Ces données sont passées en
  quatrième et cinquième arguments du constructeur de
  <code>Hoa\Console\Processus</code>. Ainsi :</p>
  <pre><code class="language-php">$processus = new Hoa\Console\Processus(
    'php',
    null, /* no option         */
    null, /* use default pipes */
    '/tmp',
    [
        'FOO'  => 'bar',
        'BAZ'  => 'qux',
        'PATH' => '/usr/bin:/bin'
    ]
);
$processus->on('input', function (Hoa\Event\Bucket $bucket) {
    $bucket->getSource()->writeAll(
        '&amp;lt;?php' . "\n" .
        'var_dump(getcwd());' . "\n" .
        'print_r($_ENV);'
    );

    return false;
});
$processus->on('output', function (Hoa\Event\Bucket $bucket) {
    $data = $bucket->getData();
    echo '> ', $data['line'], "\n";

    return;
});
$processus->run();

/**
 * Will output:
 *     > string(12) "/tmp"
 *     > Array
 *     > (
 *     >     [FOO] => bar
 *     >     [PATH] => /usr/bin:/bin
 *     >     [PWD] => /tmp
 *     >     [BAZ] => qux
 *     >     [_] => /usr/bin/php
 *     >
 *     > )
 */</code></pre>
  <p>Si le <em lang="en">current working directory</em> n'est pas précisé, nous
  utiliserons le même que le programme. Si aucun environnement n'est précisé, le
  processus utilisera celui de son parent.</p>
  <p>Nous pouvons aussi imposer un <strong>temps maximum</strong> de
  <strong>réponse</strong> en seconde au processus (défini à 30 secondes par
  défaut). C'est le dernier argument du constructeur. Nous pouvons utiliser la
  méthode <code>Hoa\Console\Processus::setTimeout</code>. Pour savoir quand ce
  temps est atteint, nous devons utiliser l'écouteur <code>timeout</code>.
  Aucune action ne sera faite automatiquement. Nous pouvons par exemple terminer
  le processus grâce à la méthode <code>Hoa\Console\Processus::terminate</code>.
  Ainsi :</p>
  <pre><code class="language-php">$processus = new Hoa\Console\Processus('php');

// 3 seconds is enough…
$processus->setTimeout(3);

// Sleep 10 seconds.
$processus->on('input', function (Hoa\Event\Bucket $bucket) {
    $bucket->getSource()->writeAll('&amp;lt;?php sleep(10);');

    return false;
});

// Terminate the processus on timeout.
$processus->on('timeout', function (Hoa\Event\Bucket $bucket) {
    echo 'TIMEOUT, terminate', "\n";
    $bucket->getSource()->terminate();

    return;
});

$processus->run();

/**
 * Will output (after 3 secondes):
 *     TIMEOUT, terminate
 */</code></pre>
  <p>Aucun action n'est réalisée automatiquement car elles peuvent être
  nombreuses. Nous pouvons peut-être débloquer le processus, le fermer pour en
  ouvrir un autre, émettre des rapports etc.</p>
  <p>À propos de la méthode <code>terminate</code>, elle peut prendre plusieurs
  valeurs différentes, définies par les constantes de
  <code>Hoa\Console\Processus</code> : <code>SIGHUP</code>, <code>SIGINT</code>,
  <code>SIGQUIT</code>, <code>SIGABRT</code>, <code>SIGKILL</code>,
  <code>SIGALRM</code> et <code>SIGTERM</code> (par défaut). Plusieurs
  <strong>signaux</strong> peuvent être envoyés aux processus pour qu'ils
  s'arrêtent. Pour avoir le détail, voir
  <a href="http://freebsd.org/cgi/man.cgi?query=signal"
     title="signal(3), FreeBSD Library Functions Manual">la page
     <code>signal</code></a>.</p>

  <h3 id="Miscellaneous" for="main-toc">Miscellaneous</h3>

  <p>Les méthodes statiques <code>getTitle</code> et <code>setTitle</code> sur
  la classe <code>Hoa\Console\Processus</code> permettent respectivement
  d'obtenir et de définir le titre du processus. Ainsi :</p>
  <pre><code class="language-php">Hoa\Console\Processus::setTitle('hoa #1');</code></pre>
  <p>Et dans un autre terminal :</p>
  <pre data-line="2"><code class="language-shell">$ ps | grep hoa
69578 ttys006    0:00.01 hoa #1
70874 ttys008    0:00.00 grep hoa</code></pre>
  <p>Ces méthodes sont très pratiques lorsque nous manipulons beaucoup de
  processus et que nous voulons les identifier efficacement (par exemple avec
  des outils comme <code>top</code> ou <code>ps</code>). Notons qu'elles ne sont
  fonctionnelles que si vous avez PHP5.5 au minimum.</p>
  <p>Une autre méthode statique intéressante est
  <code>Hoa\Console\Processus::locate</code> qui permet de déterminer le chemin
  vers un programme. Par exemple :</p>
  <pre><code class="language-php">var_dump(Hoa\Console\Processus::locate('php'));

/**
 * Could output:
 *     string(12) "/usr/bin/php"
 */</code></pre>
  <p>Dans le cas où le programme n'est pas trouvé, <code>null</code> sera
  retournée. Cette méthode se base sur le <code>PATH</code> de votre
  système.</p>

  <h3 id="Interactive_processus_and_pseudo-terminals" for="main-toc">Processus
  interactifs et pseudo-terminaux</h3>

  <p>Cette section est un peu plus technique mais explique un
  <strong>problème</strong> qui peut arriver avec certains processus dits
  <strong>interactifs</strong>.</p>
  <p>La classe <code>Hoa\Console\Processus</code> permet d'automatiser
  l'interaction avec des processus très facilement. Toutefois, ce n'est pas
  toujours possible de créer cette automatisation, à cause du comportement du
  processus. Nous allons illustrer le problème en écrivant le fichier
  <code>Interactive.php</code> :</p>
  <pre><code class="language-php">&amp;lt;?php

echo 'Login: ';

if (false === $login = fgets(STDIN)) {
    fwrite(STDERR, 'Hmm, no login.' . "\n");
    exit(1);
}

echo 'Password: ';

if (false === $password = fgets(STDIN)) {
    fwrite(STDERR, 'Hmm, no password.' . "\n");
    exit(2);
}

echo 'Result:', "\n\t", $login, "\t", $password;</code></pre>
  <p>Exécutons ce processus pour voir ce qu'il fait :</p>
  <pre><code class="language-shell">$ php Interactive.php
Login: myLogin
Password: myPassword
Result:
    myLogin
    myPassword</code></pre>
  <p>Et maintenant, automatisons l'exécution de ce processus :</p>
  <pre><code class="language-shell">$ echo 'myLogin\nmyPassword' > data
$ php Interactive.php &amp;lt; data
Login: Password: Result:
    myLogin
    myPassword</code></pre>
  <p>Excellent. Nous pourrions avoir le même résultat avec
  <code>Hoa\Console\Processus</code> sans problème. Maintenant, si notre
  processus veut s'assurer que <code>STDIN</code> est vide entre deux entrées,
  il peut ajouter :</p>
  <pre data-line-offset="7" data-line="10"><code class="language-php">}

fseek(STDIN, 0, SEEK_END);

echo 'Password: ';</code></pre>
  <p>Et alors dans ce cas, si nous essayons d'automatiser l'exécution :</p>
  <pre><code class="language-shell">$ php Interactive.php &amp;lt; data
Login: Password: Hmm, no password.</code></pre>
  <p>C'est un comportement tout à fait normal, mais
  <code>Hoa\Console\Processus</code> ne peut rien faire pour remédier à ce
  problème.</p>
  <p>La solution serait d'utiliser un
  <a href="https://en.wikipedia.org/wiki/Pseudo_terminal">pseudo-terminal</a> en
  utilisant les fonctions PTY (voir
  <a href="http://kernel.org/doc/man-pages/online/pages/man7/pty.7.html"
     title="pty(7), Linux Programmer's Manual">dans Linux</a> ou
  <a href="http://freebsd.org/cgi/man.cgi?query=pty"
     title="pty(3), FreeBSD Library Functions Manual" >dans FreeBSD</a>).
  Malheureusement ces fonctions ne sont pas disponibles dans PHP pour des
  raisons techniques. Il n'y a pas de solution possible en PHP pur, mais il
  est toujours envisageable d'utiliser un programme <strong>externe</strong>,
  écrit par exemple en C.</p>

  <h2 id="Conclusion" for="main-toc">Conclusion</h2>

  <p>La bibliothèque <code>Hoa\Console</code> offre des outils
  <strong>complets</strong> pour écrire des programmes adaptés à une interface
  <strong>textuelle</strong>, que ce soit l'interaction avec la fenêtre ou le
  curseur, l'interaction avec l'utilisateur grâce à un lecteur de lignes très
  personnalisable (avec de l'auto-complétion ou des raccourcis), la lecture
  d'options pour les programmes eux-mêmes, la construction de programmes
  élaborés, ou encore l'exécution, l'interaction et la communication avec des
  processus.</p>

</yield>
</overlay>
